import compiler

class EPYVisitor:
    """
        This is the beast that recursively walks the syntax tree converting it
        into ECMAScript. Very, VERY ugly. Feel free to help clean it up!
    """
    def __init__(self, epy):
        self.epy = epy
        self.src = ''

    def visitPrint(self,t):
        assert len(t.nodes) < 2, self.epy.error("Print currently only allows one argument!", t.lineno)
        for i in t.nodes:
            self.src += "document.write(str(%s) + ' ')" % self.epy.parseNode(i)

    def visitPrintnl(self,t):
        assert len(t.nodes) < 2, self.epy.error("Print currently only allows one argument!", t.lineno)
        for i in t.nodes:
            self.src += "document.write(str(%s) + '<br/>')" % self.epy.parseNode(i)

    def visitIf(self,t):
        test, body = t.tests[0]
        self.src += "if(%s) {\n" % self.epy.parseNode(test)
        self.epy.pushLevel()
        self.src += self.epy.parseNode(body)
        self.epy.popLevel()
        self.src += "%s}" % self.epy.indent()
        for test, body in t.tests[1:]:
            self.src += " else if(%s) {\n" % self.epy.parseNode(test)
            self.epy.pushLevel()
            self.src += self.epy.parseNode(body)
            self.epy.popLevel()
            self.src += "%s}" % self.epy.indent()
        if t.else_:
            self.src += " else {\n"
            self.epy.pushLevel()
            self.src += self.epy.parseNode(t.else_)
            self.epy.popLevel()
            self.src += "%s}" % self.epy.indent()

    def visitWhile(self,t):
        test = t.test
        body = t.body
        self.src += "while(%s) {\n" % self.epy.parseNode(test)
        self.epy.pushLevel()
        self.src += self.epy.parseNode(body)
        self.epy.popLevel()
        self.src += "%s}" % self.epy.indent()

    def visitFor(self,t):
        var = t.assign.name
        lst = self.epy.parseNode(t.list)
        if lst.startswith("xrange("):
            # "xrange" is optimized to use native Js for-loop
            args = t.list.args
            if len(args) == 3:
                start = self.epy.parseNode(args[0])
                end = self.epy.parseNode(args[1])
                step = self.epy.parseNode(args[2])
                self.src += "for(var %s = %s; %s < %s; %s += %s) {\n" % (var, start, var, end, var, step)
            elif len(args) == 2:
                start = self.epy.parseNode(args[0])
                end = self.epy.parseNode(args[1])
                self.src += "for(var %s = %s; %s < %s; ++%s) {\n" % (var, start, var, end, var)
            elif len(args) == 1:
                end = self.epy.parseNode(args[0])
                self.src += "for(var %s = 0; %s < %s; ++%s) {\n" % (var, var, end, var)
            self.epy.pushLevel()
            self.epy.pushNS()
            self.epy.addVar(var)
            self.src += self.epy.parseNode(t.body)
            self.epy.popNS()
            self.epy.popLevel()
            self.src += "%s}" % self.epy.indent()
        else:
            # All other iterations are done with try/catch for-loop of iterator
            itr = "__epy_iter_%s" % var
            self.src += "for(var %s = %s.__iter__();;) {\n" % (itr, lst)
            self.epy.pushLevel()
            self.epy.pushNS()
            self.epy.addVar(var)
            self.src += "%stry {\n" % self.epy.indent()
            self.epy.pushLevel()
            self.src += "%svar %s = %s.next();\n" % (self.epy.indent(), var, itr)
            self.src += self.epy.parseNode(t.body)
            self.epy.popLevel()
            self.src += "%s} catch(e) {\n" % self.epy.indent()
            self.epy.pushLevel()
            self.src += "%sif(e == \"StopIteration\") break;\n" % self.epy.indent()
            self.src += "%selse throw e;\n" % self.epy.indent()
            self.epy.popLevel()
            self.src += "%s}\n" % self.epy.indent()
            self.epy.popNS()
            self.epy.popLevel()
            self.src += "%s}" % self.epy.indent()

    def visitCompare(self,t):
        args = t.getChildNodes()
        self.src += "(%s" % self.epy.parseNode(args[0])
        for i in t.ops:
            self.src += " %s %s" % (i[0],  self.epy.parseNode(i[1]))
        self.src += ")"

    def opBase(self,t,op):
        args = t.getChildNodes()
        lnode, rnode = [self.epy.parseNode(arg) for arg in args[:2]]
        if all([isinstance(arg, compiler.ast.Const) for arg in args]):
            # Optimize constants be pre-evaluating const-const expressions
            result = eval(lnode + op + rnode)
            if isinstance(result, str):
                result = "\"%s\"" % result
            self.src += "(%s)" % str(result)
        else:
            self.src += "(%s %s %s)" % (lnode, op, rnode)

    def visitOr(self,t):
        self.opBase(t, "||")

    def visitAnd(self,t):
        self.opBase(t, "&&")

    def visitBitor(self,t):
        self.opBase(t, "|")

    def visitBitand(self,t):
        self.opBase(t, "&")

    def visitBitxor(self,t):
        self.opBase(t, "^")

    def visitLeftShift(self,t):
        self.opBase(t, "<<")

    def visitRightShift(self,t):
        self.opBase(t, ">>")

    def visitAdd(self,t):
        self.opBase(t, "+")

    def visitSub(self,t):
        self.opBase(t, "-")

    def visitMul(self,t):
        self.opBase(t, "*")

    def visitDiv(self,t):
        self.opBase(t, "/")

    def visitMod(self,t):
        """
            Mod operator also performs string formating!
        """
        if isinstance(t.left, compiler.ast.Const) and isinstance(t.left.value, str):
            string = self.epy.parseNode(t.left)
            args = self.epy.parseNode(t.right)
            self.src += "pyStrFormat(%s, %s)" % (string, args)
        else:
            self.opBase(t, "%")

    def visitNot(self,t):
        self.src += "!(%s)" % self.epy.parseNode(t.expr)

    def visitUnarySub(self,t):
        self.src += "-(%s)" % self.epy.parseNode(t.expr)

    def visitList(self,t):
        self.src += "list("
        args = ""
        for node in t.nodes:
            args += "%s, " % self.epy.parseNode(node)
        self.src += "%s)" % args[:-2]

    def visitTuple(self,t):
        self.src += "tuple("
        args = ""
        for node in t.nodes:
            args += "%s, " % self.epy.parseNode(node)
        self.src += "%s)" % args[:-2]

    def visitDict(self,t):
        self.src += "dict({"
        args = ""
        for node in t.items:
            args += "%s:%s, " % (self.epy.parseNode(node[0]), self.epy.parseNode(node[1]))
        self.src += "%s})" % args[:-2]

    def visitLambda(self,t):
        self.src += "function("
        self.epy.pushLevel()
        self.epy.pushNS()
        args = ""
        for arg in t.argnames:
            args += "%s, " % arg
            self.epy.addVar(arg)
        self.src += "%s) {\n%sreturn " % (args[:-2], self.epy.indent())
        self.src += self.epy.parseNode(t.code)
        self.epy.popNS()
        self.epy.popLevel()
        self.src += ";\n%s}" % self.epy.indent()

    def visitFunction(self,t):
        assert not t.decorators, self.epy.error("Decorators not supported!", t.lineno)
        self.epy.addVar(t.name) # Register function label in namespace
        if self.epy.isModule and self.epy.blocklevel == 1:
            self.src += "epy_module.%s = function(" % t.name
        else:
            self.src += "function %s(" % t.name
        args = ""
        self.epy.pushLevel()
        self.epy.pushNS()
        for arg in t.argnames:
            args += "%s, " % arg
            self.epy.addVar(arg)
        self.src += "%s) {\n" % args[:-2]
        self.src += self.epy.parseNode(t.code)
        self.epy.popNS()
        self.epy.popLevel()
        self.src += "%s}" % self.epy.indent()

    def visitClass(self,t):
        """
            The methods and "__init__" are generated separately so the
            constructor can be used in the Javascript class function
        """
        code = ""
        constructArgs = ""
        constructCode = ""
        self.epy.addVar(t.name) # Register class label in namespace
        self.epy.pushLevel()
        self.epy.pushNS()
        for m in t.code.getChildNodes():
            assert not m.decorators, self.epy.error("Decorators not supported!", m.lineno)
            assert not(m.name == "__del__"), self.epy.error("__del__ method not supported!", m.lineno)
            assert not(m.name == "__call__"), self.epy.error("__call__ method not supported!", m.lineno)
            if m.name == "__init__":
                for arg in m.argnames[1:]:
                    constructArgs += "%s, " % arg
                constructCode += self.epy.parseNode(m.code)
            else:
                code += "%sself.%s = function(" % (self.epy.indent(), m.name)
                args = ""
                self.epy.pushLevel()
                self.epy.pushNS()
                for arg in m.argnames[1:]:
                    args += "%s, " % arg
                    self.epy.addVar(arg)
                code += "%s) {\n" % args[:-2]
                code += self.epy.parseNode(m.code)
                self.epy.popNS()
                self.epy.popLevel()
                code += "%s};\n" % self.epy.indent()

        if self.epy.isModule and self.epy.blocklevel == 2:
            self.src += "epy_module.%s = function(%s) {\n" % (t.name, constructArgs[:-2])
        else:
            self.src += "function %s(%s) {\n" % (t.name, constructArgs[:-2])
        self.src += "%svar self = new Object();\n" % self.epy.indent()
        self.src += "%s%s" % (code, constructCode)
        self.src += "%sreturn self;\n" % self.epy.indent()
        self.epy.popNS()
        self.epy.popLevel()
        self.src += "%s}" % self.epy.indent()

    def visitReturn(self,t):
        self.src += "return %s" % self.epy.parseNode(t.value)

    def visitContinue(self,t):
        self.src += "continue"

    def visitCallFunc(self,t):
        label = self.epy.parseNode(t.node)
        args = t.args
        params = ""
        for arg in args:
            params += self.epy.parseNode(arg) + ", "
        if self.epy.isModule and (label in self.epy.namespace[0]):
            self.src += "epy_module.%s(%s)" % (label, params[:-2])
        else:
            self.src += "%s(%s)" % (label, params[:-2])

    def visitName(self,t):
        if t.name == "__EPY_Indices":
            self.src += "[]"
        if t.name == "__EPY_DollarSign":
            self.src += "$"
        else:
            if self.epy.isModule and (self.epy.blocklevel==1) and self.epy.isDeclared(t.name):
                self.src += "epy_module.%s" % t.name
            else:
                if self.epy.isModule and (t.name in self.epy.namespace[0]):
                    self.src += "epy_module.%s" % t.name
                else:
                    self.src += t.name

    def visitConst(self,t):
        value = t.value
        if type(value) == str:
            value = "\"%s\"" % value
        self.src += str(value)
 
    def visitStmt(self, t):
        for i in t:
            self.src += "%s%s;\n" % (self.epy.indent(), self.epy.parseNode(i))

    def visitAssTuple(self, t):
        assert 0, self.epy.error("Left-hand tuple assignment not yet implemented!", t.lineno)
 
    def visitAssName(self, t):
        if self.epy.isDeclared(t.name):
            if self.epy.isModule and self.epy.blocklevel == 1:
                self.src += "epy_module.%s = " % t.name
            else:
                if self.epy.isModule and (t.name in self.epy.namespace[0]):
                    self.src += "epy_module.%s = " % t.name
                else:
                    self.src += "%s = " % t.name
        else:
            if self.epy.isModule and self.epy.blocklevel == 1:
                self.src += "epy_module.%s = " % t.name
            else:
                self.src += "var %s = " % t.name
            self.epy.addVar(t.name)

    def visitAssign(self,t):
        val = self.epy.parseNode(t.expr)
        for node in t.nodes:
            # Special assignment for subscript and slice
            if isinstance(node, compiler.ast.Subscript):
                self.src += "%s%s)" % (self.epy.parseNode(node), val)
            elif isinstance(node, compiler.ast.Slice):
                self.src += "%s%s)" % (self.epy.parseNode(node), val)
            else:
                self.src += self.epy.parseNode(node) + val

    def visitAugAssign(self,t):
        left = self.epy.parseNode(t.node)
        op = t.op
        right = self.epy.parseNode(t.expr)
        if ".__getitem__(" in left:
            self.epy.warn("Increment operator used with __getitem__!", t.lineno)
        self.src += "%s %s %s" % (left, op, right)


    def visitAssAttr(self, t):
        self.src += "%s.%s = " % (self.epy.parseNode(t.expr), t.attrname)

    def visitGetattr(self,t):
        self.src += "%s.%s" % (self.epy.parseNode(t.expr), t.attrname)

    def visitSubscript(self,t):
        assert len(t.subs) == 1, self.epy.error("Must have exactly one subscript!", t.lineno)
        sub = t.subs[0]
        if t.flags == "OP_ASSIGN":
            self.src += "%s.__setitem__(%s, " % (self.epy.parseNode(t.expr), self.epy.parseNode(sub))
        elif t.flags == "OP_APPLY":
            if isinstance(sub, compiler.ast.Sliceobj):
                self.src += "pyGetSlice(%s, %s)" % (self.epy.parseNode(t.expr), self.epy.parseNode(sub))
            else:
                self.src += "%s.__getitem__(%s)" % (self.epy.parseNode(t.expr), self.epy.parseNode(sub))
        elif t.flags == "OP_DELETE":
            self.src += "%s.__delitem__(%s)" % (self.epy.parseNode(t.expr), self.epy.parseNode(sub))


    def visitImport(self,t):
        for name in t.names:
            self.epy.modules.append(name[0])
            self.src += "var %s = epy_module_%s()" % (name[0], name[0])

    def visitBreak(self,t):
        self.src += "break"

    def visitListCompIf(self,t):
        self.src += self.epy.parseNode(t.test)

    def visitListComp(self,t):
        expr = self.epy.parseNode(t.expr)
        lst = self.epy.parseNode(t.quals[0].list)
        if len(t.quals[0].ifs) == 0:
            ifs = None
        else:
            ifs = self.epy.parseNode(t.quals[0].ifs[0])
        assign = t.quals[0].assign.name
        if ifs:
            self.src += "pyListComp(%s, function(%s) {return %s;}, function(%s) {return %s;})" % (lst, assign, expr, assign, ifs)
        else:
            self.src += "pyListComp(%s, function(%s) {return %s;}, null)" % (lst, assign, expr)

    def visitSlice(self,t):
        assign = self.epy.parseNode(t.expr)
        lower = self.epy.parseNode(t.lower or compiler.ast.Const(0))
        upper = self.epy.parseNode(t.upper or compiler.ast.Const(0))
        if t.flags == "OP_ASSIGN":
            self.src += "%s = pySetSlice(%s, %s, %s, " % (assign, assign, lower, upper)
        elif t.flags == "OP_APPLY":
            self.src += "pyGetSlice(%s, %s, %s, 1)" % (assign, lower, upper)

    def visitSliceobj(self,t):
        nodes = t.getChildNodes()
        start = self.epy.parseNode(nodes[0])
        end = self.epy.parseNode(nodes[1])
        step = self.epy.parseNode(nodes[2])
        if start == "None":
            start = "0"
        if end == "None":
            end = "0"
        self.src += "%s, %s, %s" % (start, end, step)

    def visitTryExcept(self,t):
        self.src += "try {\n"
        self.epy.pushLevel()
        self.epy.pushNS()
        self.src += self.epy.parseNode(t.body)
        self.epy.popNS()
        self.epy.popLevel()
        self.src += "%s} catch(e) {\n" % self.epy.indent()
        self.epy.pushLevel()
        self.epy.pushNS()
        for handler in t.handlers:
            # This part is far from efficient
            # Should generate a set of "if" / "else if" instead of just "if"
            if handler[0]:
                # If the exception has a catch, use it:
                self.src += "%sif(e == %s) {\n" % (self.epy.indent(), self.epy.parseNode(handler[0]))
                self.epy.pushLevel()
                self.src += self.epy.parseNode(handler[2])
                self.epy.popLevel()
                self.src += "%s}\n" % self.epy.indent()
            else:
                # Otherwise, dont:
                self.src += self.epy.parseNode(handler[2])
        self.epy.popNS()
        self.epy.popLevel()
        self.src += "%s}" % self.epy.indent()

    def visitRaise(self,t):
        self.src += "throw \"%s\"" % self.epy.parseNode(t.expr1)

